Skip to content

Instantly share code, notes, and snippets.

@agi
Last active January 31, 2024 13:34
Show Gist options
  • Save agi/c900f3e473ff681158c0c907e34780e4 to your computer and use it in GitHub Desktop.
Save agi/c900f3e473ff681158c0c907e34780e4 to your computer and use it in GitHub Desktop.

Fission for GeckoView engineers

This document gives a brief introduction to Fission specifically targeted for engineers who work on GeckoView. For more information, also this presentation by :nika and this presentation by :mconley and :felipe.

Introduction

Web browsers run a lot of untrusted code from various sources who don't trust each other, often within a single Web Page. To limit the inpact of security vulnerabilities, Gecko isolates each piece of untrusted code in its own process. The project to migrate Gecko to this architecture is called Fission.

Before Fission

Initially, all Gecko code, including browser and Web content code, ran inside the same process.

Running everything in one process has many drawbacks, so Mozilla embarked a major refactor of the code base, called Electrolysis or E10S for short, to split the code in two processes:

  • the "main" or "parent" process which would run all the browser code
  • and the "child" or "content" process which would run all the Web content code

This allowed Gecko to sandbox any RCE (Remote Code Execution) vulnerability and allow the browser to recover from content crashes more gracefully, as a content process crash would not bring down the whole browser.

A subsequent change to the architecture, dubbed "multi-e10s", brought multiple content processes to Gecko. With this change, Gecko would run each tab in its own process until a limit is reached, after which content processes would be reused.

This change allowed Gecko to reduce even further the inpact of RCEs and crashes but not eliminate them completely.

Third-party iframes, Spectre and Meltdown

A common pattern on the Web is to have a Website's code run in the main document and load remotely-fetched ads from third-party inside iframes, which are embedded in the Web page.

Normally, iframes don't have access to the embedding page, for security reasons. Before the Fission project, this security boundary was enforced with in-process checks, and code from multiple origins ran along side one another in the same process.

In 2018, however, a number of security flaws in most CPUs were discovered, named Spectre and Meltdown, which illustrated how any in-process check is foundamentally breakable and unreliable.

In light of this discovery, it became even more clear how separating each origin's code was necessary to ensure security in Gecko.

Fission

The Fission project's aim is to isolate code from each origin's in its own process. This is especially tricky when code from two or more origin's code coexist in the same page, as described earlier, through third-party iframes. Or when Web features expose access to other tabs, like window.opener.

The main consequence of Fission for the front-end is that it's not possible anymore to inspect the entire DOM in one process, as parts of the DOM might be inside a third-party iframe which is loaded on a different process.

BrowsingContext

BrowsingContext diagram

Each JavaScript global window object is assigned to a BrowsingContext object. The BrowsingContext objects are organized in tree structure, where the root BrowsingContext is the top-level window and each child of the root is an iframe in the top-level window and so on.

The BrowsingContext object has several properties associated to it which are available from every process and are synched across processes. If the BrowsingContext is associated to a frame that is in-process, it also gives access to the docShell which exposes the document and the entire DOM for the sub-tree.

Actors

The main way to write content-process JavaScript code is by extending JSWindowActor. Each Fission top-level document is assigned a parent-side Actor and one child-side actor for each frame. Each actor has a static declaration similar to the following:

ContentDelegate: {
  parent: {
    moduleURI: "resource:///actors/ContentDelegateParent.jsm",
  },  
  child: {
    moduleURI: "resource:///actors/ContentDelegateChild.jsm",
    events: {
      DOMContentLoaded: {}, 
      // ...
      contextmenu: { capture: true },
    },  
  },  
  allFrames: true,
},

Actors loaded lazily whenever the events they respond to are fired, and are recreated at every navigation, so they cannot hold data that is supposed to outlive navigations.

GeckoView provides an extension of JSWindowActor: GeckoViewActorChild and GeckoViewActorParent which provide common getters like eventDispatcher.

Modules and Actors

GeckoView's JavaScript Front-End introduces a third type of code unit, called Module. A module instance lives on the parent process and its lifetime matches the lifetime of the app. The module usually contains data and code for actions that span multiple page loads.

Each feature in GeckoView will thus have a Module, a parent-side Actor and (potentially) several child-side Actors.

GeckoView also provides two types of features:

  • enable features are pluggable features that are only available when the embedder installs the corresponding delegate.
  • init features are always available and power internal features of GeckoView regardless of whether a delegate is installed or not.

The naming convention for modules and actors is the following:

  • init feature.
    • Module: GeckoView<FeatureName>
    • Parent actor: GeckoView<FeatureName>Parent
    • child actor: GeckoView<FeatureName>Child
  • enable feature.
    • Module: GeckoView<FeatureName>
    • Parent actor: <FeatureName>DelegateParent
    • child actor: <FeatureName>DelegateChild

Similarly to Actors, Modules are declared statically in geckoview.js, e.g.

{
  name: "GeckoViewContent",
  onInit: {
    resource: "resource://gre/modules/GeckoViewContent.jsm",
    actors: {
      GeckoViewContent: {
        // ... 
      },
    },
  },
  onEnable: {
    actors: {
      ContentDelegate: {
        // ...
      },
    },
  },
},

Messagging

Messaging diagram

To exchange information, Child actors can talk to the Parent actor using sendAsyncMessage or sendQuery, for example:

this.sendAsyncMessage("GeckoView:DOMFullscreenRequest", {});

Note that child actors should only communicate with the corresponding parent actor.

Parent and child actors can communicate with modules and the Java layer using eventDispatcher, for example:

this.eventDispatcher.sendRequest({
  type: "GeckoView:DOMMetaViewportFit",
  viewportfit: viewportFit,
});

When collecting data from a Web page, a common pattern is to aggregate all the data from child Actors in the parent Actor or in the Module, depending whether the data is specific to the current page or needs to persist across page loads.

@calumozilla
Copy link

What sort of GV features are init vs enabled?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment